/*
 * Copyright (C) 2012 Caldia Apps.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.caldia.android.frameworkG1.preference;

import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

import org.caldia.android.frameworkG1.DB.BaseColumns;
import org.caldia.android.frameworkG1.messagingcontroller.MessagingController;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;

/**
 * プリファレンス（オプション）
 *
 * @author k-matsuda@caldia.org
 *
 */
public class MyPreference {

	/**
	 * インスタンス
	 */
	private static MyPreference sInst;

	/**
	 * メッセージコントローラー
	 */
	private final MessagingController<PreferenceChangeListener> mController;

	/**
	 * DAO
	 */
	private final PreferenceDbHelper mDAO;

	/**
	 * コンストラクタ
	 */
	protected MyPreference(final Context context) {
		mController = PreferenceController.getInstance();
		mDAO = new PreferenceDbHelper(context);
	}

	/**
	 * インスタンス取得
	 *
	 * @param context
	 *            コンテキスト
	 * @return インスタンス
	 */
	public static synchronized MyPreference getInstance(final Context context) {

		if (sInst == null) {
			sInst = new MyPreference(context);
		}

		return sInst;
	}

	/**
	 * Preference 変更リスナー登録
	 *
	 * @param l
	 *            インスタンス
	 */
	public void addPreferenceChangedListener(final PreferenceChangeListener l) {
		mController.addListener(l);
	}

	/**
	 * Preference 変更リスナー登録解除
	 *
	 * @param l
	 *            登録したリスナーインスタンス
	 */
	public void removePreferenceChangedListener(final PreferenceChangeListener l) {
		try {
			mController.removeListener(l);
		} catch (final Exception e) {
//			CLog.d(this, 0, "", e);
			e.printStackTrace();
		}
	}

	/**
	 * 削除
	 *
	 * @param mymeType
	 *            mimeType
	 * @param key
	 *            キー
	 * @return 削除対象があり、削除成功した場合はtrue
	 */
	public boolean remove(final String key, final PreferenceChangeListener l) {
		return remove(PreferenceColumns.CONTENT_ITEM_TYPE, key, l);
	}
	/**
	 * 削除
	 *
	 * @param mymeType
	 *            mimeType
	 * @param key
	 *            キー
	 * @return 削除対象があり、削除成功した場合はtrue
	 */
	public boolean remove(final String mymeType, final String key,
			final PreferenceChangeListener l) {

		// ブロッキングキュー
		final BlockingQueue<Integer> queue = new SynchronousQueue<Integer>();

		final String description = "remove preference";
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {

				final String whereClause = BaseColumns.MIMETYPE + "=? and "
						+ PreferenceColumns.KEY + "=?";
				final String[] whereArgs = {mymeType, key};
				try {
					queue.put(mDAO.delete(whereClause, whereArgs));
				} catch (final InterruptedException e) {
//					CLog.e(this, 0, "", e);
					e.printStackTrace();
//					throw e;
				}

			}
		};

		mController.putCommand(description, runnable, l);

		try {
			queue.take();

			new Thread(new Runnable() {

				@Override
				public void run() {
					final Set<PreferenceChangeListener> listeners = mController
							.getListeners(l);
					for (final PreferenceChangeListener ls : listeners) {
						try {
							ls.onPreferenceRemoved(description, key);
						} catch (final Exception e) {
//							CLog.e(this, 0, "", e);
							e.printStackTrace();
						}
					}
				}
			}).start();

		} catch (final InterruptedException e) {
//			CLog.e(this, 0, "", e);
			return false;
		}

		return true;

	}

	/**
	 * テキスト保存
	 *
	 * @param key
	 *            キー
	 * @param text
	 *            テキスト
	 * @param l
	 *            リスナー
	 */
	public void putString(final String key, final String text,
			final PreferenceChangeListener l) {

		putString(PreferenceColumns.CONTENT_ITEM_TYPE, key, text, l);

	}

	/**
	 * 拡張
	 *
	 * @param mimeType
	 *            mimeType
	 * @param key
	 *            key
	 * @param values
	 *            値（key, mimeType含んでなくて構わない)
	 * @param l
	 *            null 可能
	 */
	public void putValues(final String mimeType, final String key,
			final ContentValues values, final PreferenceChangeListener l) {
		// ブロッキングキュー
		final BlockingQueue<String> queue = new SynchronousQueue<String>();

		final String description = "update preference";

		// 処理内容
		final Runnable action = new Runnable() {

			@Override
			public void run() {

				// 更新値作成
				values.put(BaseColumns.MIMETYPE, mimeType);
				values.put(PreferenceColumns.KEY, key);

				// 条件
				final String whereClause = BaseColumns.MIMETYPE + "=? and "
						+ PreferenceColumns.KEY + "=?";
				// 条件値
				final String[] whereArgs = {mimeType, key};

				// 更新
				final int count = mDAO.update(values, whereClause, whereArgs);

//				CLog.dt(this, 0, "更新数 " + count);

				if (count == 0) {

					// 更新失敗
//					CLog.dt(this, 0, "更新失敗、挿入開始");

					// 挿入
					final long id = mDAO.insert(values);
					if (id < 0) {

//						CLog.dt(this, 0, "挿入失敗");

						// 挿入失敗
						throw new RuntimeException("id is " + id);
					} else {
						// 挿入成功
//						CLog.dt(this, 0, "挿入成功");

						new Thread(new Runnable() {

							@Override
							public void run() {
								final Set<PreferenceChangeListener> listeners = mController
										.getListeners(l);
								for (final PreferenceChangeListener ls : listeners) {
									try {

										ls.onPreferenceInserted(description,
												key);

									} catch (final Exception e) {
//										CLog.d(this, 0, "", e);
									}
								}

							}
						}).start();

					}

				} else {
					// 更新成功
//					CLog.dt(this, 0, "更新成功");
				}

				new Thread(new Runnable() {

					@Override
					public void run() {

						final Set<PreferenceChangeListener> listeners = mController
								.getListeners(l);
						for (final PreferenceChangeListener ls : listeners) {
							try {

								ls.onPreferenceUpdated(description, key);

							} catch (final Exception e) {
//								CLog.d(this, 0, "", e);
								e.printStackTrace();
							}
						}

					}
				}).start();

				try {
					queue.put("done");
				} catch (final InterruptedException e) {
//					CLog.e(this, 0, "", e);
					e.printStackTrace();
				}

			}
		};

		// メッセージコントローラースレッドで実行
		mController.putCommand(description, action, l);

		try {
			queue.take();
		} catch (final InterruptedException e) {
//			CLog.e(this, 0, "", e);
		}
	}

	/**
	 * ContentValues に変換して取得する
	 *
	 * @param mimeType
	 *            mimeType
	 * @param key
	 *            key
	 * @return ContentValues
	 */
	public ContentValues getValues(final String mimeType, final String key,
			final String[] columns) {
		// ブロッキングキュー
		final BlockingQueue<ContentValues> queue = new SynchronousQueue<ContentValues>();

		final String description = "get values";

		// 処理内容
		final Runnable action = new Runnable() {

			@Override
			public void run() {

				final ContentValues values = new ContentValues();

				// 条件
				final String whereClause = BaseColumns.MIMETYPE + "=? and "
						+ PreferenceColumns.KEY + "=?";
				// 条件値
				final String[] whereArgs = {mimeType, key};

				Cursor c = null;
				c = mDAO.query(false, columns, whereClause, whereArgs, null,
						null, null, "1");

				if (c != null) {

					if (c.moveToFirst()) {

						final int count = c.getColumnCount();
						for (int i = 0; i < count; i++) {

							try {
								final String name = c.getColumnName(i);
								final String value = c.getString(i);

								values.put(name, value);

							} catch (final Exception e) {
								continue;
							}
						}

					}

					try {
						c.close();
					} catch (final Exception e) {
//						CLog.e(MyPreference.class.getName(), 0,
//								"" + e.getCause(), e);
						e.printStackTrace();
					}
				}
				try {
					queue.put(values);
				} catch (final InterruptedException e) {
//					CLog.e(this, 0, "", e);
					e.printStackTrace();
				}

			}
		};

		// メッセージコントローラースレッドで実行
		mController.putCommand(description, action,
				new PreferenceChangeListener("get values"));

		try {
			return queue.take();
		} catch (final InterruptedException e) {
//			CLog.e(this, 0, "", e);
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * テキスト保存
	 *
	 * @param mimeType
	 *            mimyType
	 * @param key
	 *            キー
	 * @param text
	 *            テキスト
	 * @param l
	 *            リスナー
	 */
	public void putString(final String mimeType, final String key,
			final String text, final PreferenceChangeListener l) {

		final ContentValues values = new ContentValues();
		values.put(PreferenceColumns.VALUE, text);
		putValues(mimeType, key, values, l);

		// // ブロッキングキュー
		// final BlockingQueue<String> queue = new SynchronousQueue<String>();
		//
		// final String description = "update preference";
		//
		// // 処理内容
		// final Runnable action = new Runnable() {
		//
		// @Override
		// public void run() {
		//
		// // 更新値作成
		// final ContentValues values = new ContentValues();
		// values.put(BaseColumns.MIMETYPE, mimeType);
		// values.put(PreferenceColumns.KEY, key);
		// values.put(PreferenceColumns.VALUE, text);
		//
		// // 条件
		// final String whereClause = BaseColumns.MIMETYPE + "=? and "
		// + PreferenceColumns.KEY + "=?";
		// // 条件値
		// final String[] whereArgs = {mimeType, key};
		//
		// // 更新
		// final int count = mDAO.update(values, whereClause, whereArgs);
		//
		// CLog.dt(this, 0, "更新数 " + count);
		//
		// if (count == 0) {
		//
		// // 更新失敗
		// CLog.dt(this, 0, "更新失敗、挿入開始");
		//
		// // 挿入
		// final long id = mDAO.insert(values);
		// if (id < 0) {
		//
		// CLog.dt(this, 0, "挿入失敗");
		//
		// // 挿入失敗
		// throw new RuntimeException("id is " + id);
		// } else {
		// // 挿入成功
		// CLog.dt(this, 0, "挿入成功");
		//
		// new Thread(new Runnable() {
		//
		// @Override
		// public void run() {
		// final Set<PreferenceChangeListener> listeners = mController
		// .getListeners(l);
		// for (final PreferenceChangeListener ls : listeners) {
		// try {
		//
		// ls.onPreferenceInserted(description,
		// key);
		//
		// } catch (final Exception e) {
		// CLog.d(this, 0, "", e);
		// }
		// }
		//
		// }
		// }).start();
		//
		// }
		//
		// } else {
		// // 更新成功
		// CLog.dt(this, 0, "更新成功");
		// }
		//
		// new Thread(new Runnable() {
		//
		// @Override
		// public void run() {
		//
		// final Set<PreferenceChangeListener> listeners = mController
		// .getListeners(l);
		// for (final PreferenceChangeListener ls : listeners) {
		// try {
		//
		// ls.onPreferenceUpdated(description, key);
		//
		// } catch (final Exception e) {
		// CLog.d(this, 0, "", e);
		// }
		// }
		//
		// }
		// }).start();
		//
		// try {
		// queue.put(text);
		// } catch (final InterruptedException e) {
		// CLog.e(this, 0, "", e);
		// }
		//
		// }
		// };
		//
		// // メッセージコントローラースレッドで実行
		// mController.putCommand(description, action, l);
		//
		// try {
		// queue.take();
		// } catch (final InterruptedException e) {
		// CLog.e(this, 0, "", e);
		// }

	}

	/**
	 * テキスト取得
	 *
	 * @param key
	 *            キー
	 * @param defValue
	 *            保存されている値が無い場合に返す値
	 * @return テキスト
	 */
	public String getString(final String key, final String defValue) {

		return getString(PreferenceColumns.CONTENT_ITEM_TYPE, key, defValue);

	}

	/**
	 * テキスト取得
	 *
	 * @param mimeType
	 *            mimeType
	 * @param key
	 *            キー
	 * @param defValue
	 *            保存されている値が無い場合に返す値
	 * @return テキスト
	 */
	public String getString(final String mimeType, final String key,
			final String defValue) {
		// ブロッキングキュー
		final BlockingQueue<String> queue = new SynchronousQueue<String>();

		final String description = "get preference";
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {

				final String[] columns = {PreferenceColumns.VALUE};
				final String selection = BaseColumns.MIMETYPE + "=? and "
						+ PreferenceColumns.KEY + "=?";
				final String[] selectionArgs = {mimeType, key};
				Cursor c = null;
				try {
					c = mDAO.query(false, columns, selection, selectionArgs,
							null, null, null, String.valueOf(1));

					if (c != null && c.moveToFirst()) {

						try {
							queue.put(c.getString(0));
						} catch (final InterruptedException e) {
//							CLog.e(this, 0, "", e);
							e.printStackTrace();
						}

					} else {

						try {
							queue.put(defValue);
						} catch (final InterruptedException e) {
//							CLog.e(this, 0, "", e);
							e.printStackTrace();
						}

					}
				} finally {
					if (c != null) {
						c.close();
					}
				}

			}
		};
		mController.putCommand(description, runnable,
				new PreferenceChangeListener());

		try {
			return queue.take();
		} catch (final InterruptedException e) {
//			CLog.e(this, 0, "", e);
			e.printStackTrace();
		}

		return defValue;

	}

	/**
	 * Int 値保存
	 *
	 * @param key
	 *            キー
	 * @param value
	 *            値
	 * @param l
	 *            プリファレンス変更リスナー
	 */
	public void putInt(final String key, final int value,
			final PreferenceChangeListener l) {

		final String text = String.valueOf(value);
		putString(key, text, l);

	}

	/**
	 * Int 値保存
	 *
	 * @param mimeType
	 *            mimeType
	 * @param key
	 *            キー
	 * @param value
	 *            値
	 * @param l
	 *            プリファレンス変更リスナー
	 */
	public void putInt(final String mimeType, final String key,
			final int value, final PreferenceChangeListener l) {
		final String text = String.valueOf(value);
		putString(mimeType, key, text, l);
	}

	/**
	 * Int 値取得
	 *
	 * @param key
	 *            キー
	 * @param def
	 *            保存されているデータが無かった場合に返す値
	 * @return 値
	 */
	public int getInt(final String key, final int def) {

		try {

			return Integer.parseInt(getString(key, String.valueOf(def)));

		} catch (final Exception e) {
//			CLog.d(this, 0, "", e);
		}
		return def;

	}

	/**
	 * Int 値取得
	 *
	 * @param mimeType
	 *            mimeType
	 * @param key
	 *            キー
	 * @param def
	 *            保存されているデータが無かった場合に返す値
	 * @return 値
	 */
	public int getInt(final String mimeType, final String key, final int def) {

		try {

			return Integer.parseInt(getString(mimeType, key,
					String.valueOf(def)));

		} catch (final Exception e) {
//			CLog.d(this, 0, "", e);
			e.printStackTrace();
		}
		return def;

	}

	/**
	 * boolean 値保存
	 *
	 * @param key
	 *            キー
	 * @param value
	 *            値
	 * @param l
	 *            プリファレンス変更リスナー
	 */
	public void putBoolean(final String key, final boolean value,
			final PreferenceChangeListener l) {

		final int val = value ? 1 : 0;
		putInt(key, val, l);

	}

	/**
	 * boolean 値保存
	 *
	 * @param mimeType
	 *            mimeType
	 *
	 * @param key
	 *            キー
	 * @param value
	 *            値
	 * @param l
	 *            プリファレンス変更リスナー
	 */
	public void putBoolean(final String mimeType, final String key,
			final boolean value, final PreferenceChangeListener l) {

		final int val = value ? 1 : 0;
		putInt(mimeType, key, val, l);

	}

	/**
	 * boolean 値取得
	 *
	 * @param key
	 *            キー
	 * @param def
	 *            保存されているデータが無かった場合に返す値
	 * @return 値
	 */
	public boolean getBoolean(final String key, final boolean def) {
		try {

			return 0 < getInt(key, def ? 1 : 0);

		} catch (final Exception e) {
//			CLog.d(this, 0, "", e);
			e.printStackTrace();
		}
		return def;
	}

	/**
	 * boolean 値取得
	 *
	 * @param mimeType
	 *            mimeType
	 * @param key
	 *            キー
	 * @param def
	 *            保存されているデータが無かった場合に返す値
	 * @return 値
	 */
	public boolean getBoolean(final String mimeType, final String key,
			final boolean def) {
		try {

			return 0 < getInt(mimeType, key, def ? 1 : 0);

		} catch (final Exception e) {
//			CLog.d(this, 0, "", e);
			e.printStackTrace();
		}
		return def;
	}

}
